package com.mamingchao.concurrent.thread_communication2;

import org.apache.commons.lang3.StringUtils;

import java.util.EmptyStackException;
import java.util.Stack;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 这里是SyncContainer 上一个版本实现的增强版；
 * 这里 实现了 容器满的时候，生产者阻塞；容器空的时候，消费者阻塞；
 *
 * 但是这里有个bug -- 并发读或者写的时候，stackContainer.size 和 stackContainer.empty() 不是线程安全的
 * 比如stackContainer.empty()，10个线程并发过来的时候，这里面 container里面只有 2个elements
 * 大家都 通过了if判断，但到后面取的时候，有的人去不到值
 *
 * Created by mamingchao on 2020/9/20.
 */
public class SyncContainer1 {

    /*
        容器固定容量 为10
     */
    final int capacity = 8;

    Stack stackContainer = new Stack();


    static volatile String signal = "";
    static final String WRITING = "write";
    static final String READING = "read";


    //存放的信号量
    Semaphore putSemaphore = new Semaphore(2);
    //读取的信号量
    Semaphore getSemaphore = new Semaphore(10);

    private synchronized int getContainerSize(){
        return stackContainer.size();
    }

    private synchronized boolean isEmpty() {
        return  this.getContainerSize() == 0 ? true : false;
    }

    /**
     * 如果容器已满，直接返回false
     * 容器的写方法
     * @param  o
     * @return true false
     */
    public boolean put(Object o) {

        //检查signal 状态，轮训check
        while (true) {
            //如果有线程正在读，那么写线程循环等待,每次等待0.1秒
            //如果容器正在被读，需要阻塞住；如果容器已经满了，也需要阻塞住
            if ((StringUtils.isNotBlank(signal) && READING.equalsIgnoreCase(signal)) || this.getContainerSize() == capacity) {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {//如果signal 是空，是signal 的初始状态，说明都还没有读过或者写过，推出while循环 可以往下读
                //或者已经是写状态，也可以继续往下走；具体能不能执行，看是否容器已满，或者能不能抢到信号量
                signal = WRITING;
                break;
            }
        }


        try {
            putSemaphore.acquire();
            stackContainer.push(o);
            TimeUnit.SECONDS.sleep(1);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        putSemaphore.release();
        return true;
    }


    /**
     * 如果容器已空，直接返回null
     * 容器的读方法
     * @return true false
     */
    public Object get() {
        //检查signal 状态，轮训check
        while (true) {
            //如果有线程正在写，那么读线程循环等待,每次等待0.1秒
            //如果容器正在被写，需要阻塞住；如果容器已经空了，也需要阻塞住
            boolean flag = this.isEmpty();
            System.out.println("Current flag is " + flag);
            if ((StringUtils.isNotBlank(signal) && WRITING.equalsIgnoreCase(signal)) || flag) {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {//如果signal 是空，是signal 的初始状态，说明都还没有读过或者写过，推出while循环 可以往下读
                //或者已经是写状态，也可以继续往下走；具体能不能执行，看是否容器已满，或者能不能抢到信号量
                signal = READING;
                break;
            }
        }

        try {
            getSemaphore.acquire();
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            Object result = stackContainer.pop();

            getSemaphore.release();
            return result;
        } catch (EmptyStackException e) {
            System.out.println("Oops, you have come into catch field !");
            getSemaphore.release();
            return null;
        }
    }




    public static void main(String[] args) {
        SyncContainer1 sc = new SyncContainer1();

        for (int i = 0; i < 10; i++) {
            String threadName = "put thread--" + i;
            String value = i + "";
            new Thread(()->{
                boolean result = sc.put(value);
                System.out.println(threadName + "  has put in " + value + " and the result is " + result);
            }).start();
        }

        for (int i = 0; i < 20; i++) {
            String threadName = "get thread--" + i;
            new Thread(()->{
                Object v = sc.get();
                System.out.println(threadName + " has get " + v);
            }).start();
        }
    }
}
